ES6中的元编程 - Proxy & Reflect
什么是元编程
元编程(Metaprogramming)是指某类计算机程序的编写,这类计算机程序编写或者操纵其他程序(或者自身)作为它们的数据,或者在运行时完成部分本应在编译时完成的工作。很多情况下与手工编写全部代码相比工作效率更高。编写元程序的语言称之为元语言,被操作的语言称之为目标语言。一门语言同时也是自身的元语言的能力称之为反射。
简单一点来理解元编程其实就是改变源码里面的东西,对其原本的功能进行了修改,能"介入"的对象底层操作进行的过程中,并加以影响。元编程中的元概念可以理解为程序本身。"元编程能让你拥有可以扩展程序本身能力"。
Proxy(代理)
用于自定义的对象的行为,比如修改set和get,感觉是es5的Object.defineProerty()方法的es6升级版
代码示例
数据劫持,验证操作
let handler = {
get: function(target, key){
return key in target ? target[key] : 37;
},
set: function(target, key, value) {
if (key === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError('The age is not an integer');
}
if (value > 200) {
throw new RangeError('The age seems invalid');
}
}
// The default behavior to store the value
target[key] = value;
}
};
let p = new Proxy({}, handler);
p.a = 1;
p.b = undefined;
person.age = 100;
console.log(person.age);
// 100
person.age = 'young';
// 抛出异常: Uncaught TypeError: The age is not an integer
person.age = 300;
// 抛出异常: Uncaught RangeError: The age seems invalid
console.log(p.a, p.b); // 1, undefined
console.log('c' in p, p.c); // false, 37
函数节流
const createThrottleProxy = (fn, rate) => {
let lastClick = Date.now() - rate;
return new Proxy(fn, {
apply(target, context, args) {
if (Date.now() - lastClick >= rate) {
fn.bind(target)(args);
lastClick = Date.now();
}
}
});
};
const handler = () => console.log('Do something...');
const handlerProxy = createThrottleProxy(handler, 1000);
document.addEventListener('scroll', handlerProxy);
Proxy 实例的方法
get(target, propKey, receiver): 拦截对象属性的读取,比如proxy.foo和proxy['foo']。
set(target, propKey, value, receiver): 拦截对象属性的设置,比如proxy.foo = v或proxy['foo'] = v,返回一个布尔值。
has(target, propKey): 拦截propKey in proxy的操作,返回一个布尔值。
deleteProperty(target, propKey): 拦截delete proxy[propKey]的操作,返回一个布尔值。
ownKeys(target): 拦截Object.getOwnPropertyNames(proxy)、 Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
getOwnPropertyDescriptor(target, propKey): 拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
defineProperty(target, propKey, propDesc): 拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一个布尔值。
preventExtensions(target): 拦截Object.preventExtensions(proxy),返回一个布尔值。 getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象。
isExtensible(target): 拦截Object.isExtensible(proxy),返回一个布尔值。
setPrototypeOf(target, proto): 拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
apply(target, object, args): 拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。
construct(target, args):
拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)。
Reflect(反射)
用于替代直接调用Object的方法,它并不是一个函数对象,并没有constructor,所以不用new操作符来实例化。
Reflect 有助于将默认操作从处理程序转发到目标。 以 Reflect.has() 为例,你可以将 in 运算符作为函数:
Reflect.has(Object, "assign"); // true
更好的 apply 函数
在 ES5 中,我们通常使用 Function.prototype.apply() 方法调用一个具有给定 this 值和 arguments 数组(或类数组对象)的函数。
Function.prototype.apply.call(Math.floor, undefined, [1.75]);
使用 Reflect.apply,这变得不那么冗长和容易理解:Reflect.apply(Math.floor, undefined, [1.75]); // 1;
Reflect.apply(String.fromCharCode, undefined, [104, 101, 108, 108, 111]); // "hello"
Reflect.apply(RegExp.prototype.exec, /ab/, ['confabulation']).index; // 4
Reflect.apply(''.charAt, 'ponies', [3]); // "i"
检查属性定义是否成功
使用 Object.defineProperty,如果成功返回一个对象,否则抛出一个 TypeError,你将使用 try...catch 块来捕获定义属性时发生的任何错误。因为 Reflect.defineProperty 返回一个布尔值表示的成功状态,你可以在这里使用 if...else 块:
if (Reflect.defineProperty(target, property, attributes)) {
// success
} else {
// failure
}